package cn.edu.neu.lab603.cloudeval.entity

import cn.edu.neu.lab603.cloudeval.CloudEval
import cn.edu.neu.lab603.cloudeval.strategy.{AllocationStrategy, ReallocationStrategy}
import cn.edu.neu.lab603.des.{SimEntity, SimEvent}
import com.typesafe.scalalogging.LazyLogging

import scala.collection.mutable.ListBuffer

/** 云资源中的供应商（数据中心）
  *
  * Created by yfwz100 on 16/9/12.
  */
class DataCenter(private[entity] val context: CloudEval,
                 val allocationStrategy: AllocationStrategy,
                 val reallocationStrategy: ReallocationStrategy) {
  private implicit val self = this

  /** 物理机列表。 */
  private val internalHosts = new ListBuffer[PhysicalMachine]

  /** 获取物理机列表。 */
  def pmList: Seq[PhysicalMachine] = internalHosts

  /** 添加物理机到当前数据中心中。
    *
    * @param hostList 物理机列表
    */
  def addPhysicalMachines(hostList: Iterable[PhysicalMachine]): Unit = {
    this.internalHosts ++= hostList.map { host =>
      host.dataCenter = this
      host
    }
  }

  /** 初始化数据中心内机器的分布 */
  def initDataCenterLayout(): Unit = {
    context.submitTaskOnNextTick(new SimEvent[CloudEval] {
      override def execute(context: CloudEval): Unit = {
        allocationStrategy.initDataCenterLayout(DataCenter.this)
      }
    })
  }

  /** 为需求分配一个虚拟机。
    * 这是一个复杂的步骤：检查是否有空闲的物理机，有则分配虚拟机；否则启动物理机，把提交任务延后执行。
    *
    * @param cloudlet 云计算用户需求
    */
  def allocateVMForCloudlet(cloudlet: CloudTask): VirtualMachine = allocationStrategy.allocateVMForCloudlet(cloudlet, this)


  /** 取消某个云计算任务分布的虚拟机。
    * 这也是一个复杂的步骤：去掉虚拟机和云计算任务的关联是一步，而是否关闭虚拟机，则取决于虚拟机分配策略。
    *
    * @param cloudlet 云计算用户需求
    */
  def deallocateVMForCloudlet(cloudlet: CloudTask): Unit = allocationStrategy.deallocateVMForCloudlet(cloudlet, this)

  /** 检测是否需要进行虚拟机的重分配。
    *
    * @return 布尔值
    */
  def checkReallocationNeeds(): Boolean = {
    reallocationStrategy.checkResourcesViolation(this)
  }

  /** 重新分配虚拟机资源。
    * 当系统检测到需要重分布的时候执行。
    */
  def reallocateVirtualMachines(): Unit = {
    val mapping = reallocationStrategy.reallocate(this)
    for ((vm, pm) <- mapping) {
      vm.migrate(pm)
    }
    reallocationStrategy.cleanup(this)
  }
}

object DataCenter {

  /** 更新数据中心。
    *
    * @param dataCenter 数据中心
    */
  class UpdateEvent(implicit val dataCenter: DataCenter) extends SimEvent[CloudEval] with LazyLogging {

    /**
      * 执行这个任务。
      */
    override def execute(context: CloudEval): Unit = {
      val clock = context.getCurrentClock

      if (dataCenter.checkReallocationNeeds()) {
        dataCenter.reallocateVirtualMachines()
      }

      if (dataCenter.internalHosts.exists(_.getStatus == PhysicalMachine.Status.ON)) {
        // update all the hosts, as each host is independent.
        dataCenter.internalHosts.foreach(_.update(clock))

        logger.debug(s"[$clock] All hosts are updated.")

        context.submitTaskOnNextTick(this)
      } else
        logger.debug(s"[$clock] Update of data center is suspended.")

    }

    /** 只能执行一次 */
    override def getMaxExecTimes: Int = 1

    override def priority: Int = -1

    /** 如果数据中心是一致的，那么就认为是相等的 */
    override def equals(obj: Any): Boolean = obj match {
      case event: UpdateEvent =>
        this.dataCenter == event.dataCenter
      case _ => false
    }

  }

  /** 关闭数据中心。 */
  class ShutdownEvent extends SimEvent[CloudEval] with LazyLogging {
    override def execute(context: CloudEval): Unit = {
      // do nothing.
    }
  }

  class NetworkTransferEvent(val packet: VirtualMachine.NetworkPacket) extends SimEvent[CloudEval] with LazyLogging {

    /** 执行这个任务。
      * 所有任务应该在一个时间戳的周期内搞定，否则应该分发多个任务执行。
      *
      * @param context 执行环境
      */
    override def execute(context: CloudEval): Unit = {
      val toVM = SimEntity.get[VirtualMachine](packet.to)
      toVM.net.received(packet)
    }
  }
}

class NoHostToAllocateException(msg:String = "") extends Exception(msg)